package com.zzl.study.nacos.properties;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.nacos.api.naming.PreservedMetadataKeys;
import com.alibaba.nacos.client.naming.utils.UtilAndComs;
import com.alibaba.spring.util.PropertySourcesUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zzl.study.nacos.NacosServiceMultiManager;
import com.zzl.study.nacos.event.NacosDiscoveryInfoChangedEvent;
import com.zzl.study.nacos.utils.InetIPv6Utils;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.alibaba.nacos.api.PropertyKeyConst.*;

/**
 * Nacos多注册中心属性配置类
 *
 * @author: zhangzl
 * @date: 2025/7/14 22:07
 * @version: 1.0
 */
@Slf4j
@Data
@ConfigurationProperties("spring.cloud.nacos.multi-discovery")
public class NacosMultiDiscoveryProperties {

    /**
     * 配置前缀
     */
    public static final String PREFIX = "spring.cloud.nacos.multi-discovery";

    /**
     * IP类型：IPV4
     */
    private static final String IPV4 = "IPv4";

    /**
     * IP类型：IPv6
     */
    private static final String IPV6 = "IPv6";

    /**
     * 匹配规则
     */
    private static final Pattern PATTERN = Pattern.compile("-(\\w)");

    /**
     * 注册到Nacos上的服务名，默认为spring.application.name
     */
    @Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}")
    private String service;

    /**
     * 服务实例权重
     */
    private float weight = 1;

    /**
     * 服务实例所属集群名
     */
    private String clusterName = "DEFAULT";

    /**
     * 服务实例所属分组名
     */
    private String group = "DEFAULT_GROUP";

    /**
     * 是否在启动时从本地缓存加载服务注册信息
     */
    private String namingLoadCacheAtStart = "false";

    /**
     * 服务实例额外的元数据信息
     */
    private Map<String, String> metadata = new HashMap<>();

    /**
     * 是否启动注册服务
     */
    private boolean registerEnabled = true;

    /**
     * 服务实例指定注册的IP地址
     */
    private String ip;

    /**
     * 服务实例注册时使用哪个网卡获取IP地址
     */
    private String networkInterface = "";

    /**
     * IP类型，默认IPV4，可以指定IPV6，但是当获取不到IPV6的地址时退回到IPV4的地址
     */
    private String ipType;

    /**
     * 服务实例指定注册的端口
     */
    private int port = -1;

    /**
     * 服务实例是否是HTTPS模式
     */
    private boolean secure = false;

    /**
     * 服务实例心跳间隔时间（毫秒）
     */
    private Integer heartBeatInterval;

    /**
     * 服务实例心跳超时时间（毫秒）
     */
    private Integer heartBeatTimeout;

    /**
     * 服务实例IP地址删除超时时间（毫秒）
     */
    private Integer ipDeleteTimeout;

    /**
     * 从Nacos服务端拉取新服务信息的间隔时间
     */
    private long watchDelay = 30000;

    /**
     * Nacos注册中心日志文件名
     */
    private String logName;

    /**
     * 服务实例是否可以接收请求
     */
    private boolean instanceEnabled = true;

    /**
     * 是否是临时实例
     */
    private boolean ephemeral = true;

    /**
     * 是否开启Nacos容错功能，若开启，则Nacos故障时，会从本地缓存获取服务实例
     */
    private boolean failureToleranceEnabled;

    /**
     * 是否在服务注册出错时抛出异常，否则仅记录错误日志
     */
    private boolean failFast = true;

    /**
     * 多注册中心配置
     */
    private Map<String, NacosServerConfiguration> multiple;

    /**
     * IPv6工具类
     */
    @Autowired
    @JsonIgnore
    private InetIPv6Utils inetIPv6Utils;

    /**
     * IP工具类
     */
    @Autowired
    @JsonIgnore
    private InetUtils inetUtils;

    /**
     * Spring环境
     */
    @Autowired
    @JsonIgnore
    private Environment environment;

    /**
     * Nacos多注册中心服务管理器
     */
    @Autowired
    @JsonIgnore
    private NacosServiceMultiManager nacosServiceMultiManager;

    /**
     * Spring应用事件发布器
     */
    @Autowired
    @JsonIgnore
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 初始化
     *
     * @throws Exception 初始化异常
     */
    @PostConstruct
    public void init() throws Exception {
        this.metadata.put(PreservedMetadataKeys.REGISTER_SOURCE, "SPRING_CLOUD");
        if (this.secure) {
            this.metadata.put("secure", "true");
        }
        this.logName = Objects.toString(this.logName, "");
        if (StringUtils.isEmpty(this.ip)) {
            if (StringUtils.isEmpty(this.networkInterface)) {
                if (this.ipType == null) {
                    this.ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
                    String ipv6Addr = inetIPv6Utils.findIPv6Address();
                    this.metadata.put(IPV6, ipv6Addr);
                    if (ipv6Addr != null) {
                        this.metadata.put(IPV6, ipv6Addr);
                    }
                } else if (IPV4.equalsIgnoreCase(this.ipType)) {
                    this.ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
                } else if (IPV6.equalsIgnoreCase(this.ipType)) {
                    this.ip = inetIPv6Utils.findIPv6Address();
                    if (StringUtils.isEmpty(this.ip)) {
                        log.warn("There is no available IPv6 found. Spring Cloud Alibaba will automatically find IPv4.");
                        this.ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
                    }
                } else {
                    throw new IllegalArgumentException("please checking the type of IP " + this.ipType);
                }
            } else {
                NetworkInterface netInterface = NetworkInterface.getByName(this.networkInterface);
                if (null == netInterface) {
                    throw new IllegalArgumentException("no such interface " + this.networkInterface);
                }
                Enumeration<InetAddress> inetAddress = netInterface.getInetAddresses();
                while (inetAddress.hasMoreElements()) {
                    InetAddress currentAddress = inetAddress.nextElement();
                    if (currentAddress instanceof Inet4Address || currentAddress instanceof Inet6Address && !currentAddress.isLoopbackAddress()) {
                        this.ip = currentAddress.getHostAddress();
                        break;
                    }
                }
                if (StringUtils.isEmpty(this.ip)) {
                    throw new RuntimeException("cannot find available ip from" + " network interface " + networkInterface);
                }
            }
        }
        this.overrideFromEnv(environment);
        this.multiple.forEach((registryName, nacosServerConfiguration) -> nacosServerConfiguration.init(registryName, environment));
        if (nacosServiceMultiManager.isNacosDiscoveryInfoChanged(this)) {
            applicationEventPublisher.publishEvent(new NacosDiscoveryInfoChangedEvent(this));
        }
        nacosServiceMultiManager.setNacosMultiDiscoveryProperties(this);
    }

    /**
     * 获取指定注册中心的配置
     *
     * @param registryName 注册中心名称
     * @return 注册中心配置
     */
    public NacosServerConfiguration getNacosDiscoveryConfiguration(String registryName) {
        NacosServerConfiguration configuration = this.multiple.get(registryName);
        Assert.notNull(configuration, "No nacos discovery configuration found for registryName: " + registryName);
        return configuration;
    }

    /**
     * 设置指定注册中心的配置
     *
     * @param registryName             注册中心名称
     * @param nacosServerConfiguration 注册中心配置
     */
    public void setNacosDiscoveryYamlConfiguration(String registryName, NacosServerConfiguration nacosServerConfiguration) {
        this.multiple.put(registryName, nacosServerConfiguration);
    }

    /**
     * 获取指定注册中心的Nacos属性
     *
     * @param registryName 注册中心名称
     * @return Nacos属性
     */
    public Properties getNacosProperties(String registryName) {
        NacosServerConfiguration nacosDiscoveryConfiguration = getNacosDiscoveryConfiguration(registryName);
        Properties properties = new Properties();
        properties.put(SERVER_ADDR, nacosDiscoveryConfiguration.getServerAddr());
        properties.put(USERNAME, Objects.toString(nacosDiscoveryConfiguration.getUsername(), ""));
        properties.put(PASSWORD, Objects.toString(nacosDiscoveryConfiguration.getPassword(), ""));
        properties.put(NAMESPACE, nacosDiscoveryConfiguration.getNamespace());
        properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, this.logName);
        String endpoint = nacosDiscoveryConfiguration.getEndpoint();
        if (endpoint.contains(":")) {
            int index = endpoint.indexOf(":");
            properties.put(ENDPOINT, endpoint.substring(0, index));
            properties.put(ENDPOINT_PORT, endpoint.substring(index + 1));
        } else {
            properties.put(ENDPOINT, endpoint);
        }
        properties.put(ACCESS_KEY, nacosDiscoveryConfiguration.getAccessKey());
        properties.put(SECRET_KEY, nacosDiscoveryConfiguration.getSecretKey());
        properties.put(CLUSTER_NAME, this.clusterName);
        properties.put(NAMING_LOAD_CACHE_AT_START, this.namingLoadCacheAtStart);
        enrichNacosDiscoveryProperties(properties, registryName);
        return properties;
    }

    public Map<String, Properties> getNacosProperties() {
        return this.multiple.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> getNacosProperties(entry.getKey())));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        NacosMultiDiscoveryProperties that = (NacosMultiDiscoveryProperties) o;
        return Float.compare(weight, that.weight) == 0
                && registerEnabled == that.registerEnabled
                && port == that.port
                && secure == that.secure
                && watchDelay == that.watchDelay
                && instanceEnabled == that.instanceEnabled
                && ephemeral == that.ephemeral
                && failureToleranceEnabled == that.failureToleranceEnabled
                && failFast == that.failFast
                && Objects.equals(service, that.service)
                && Objects.equals(clusterName, that.clusterName)
                && Objects.equals(group, that.group)
                && Objects.equals(namingLoadCacheAtStart, that.namingLoadCacheAtStart)
                && Objects.equals(metadata, that.metadata)
                && Objects.equals(ip, that.ip)
                && Objects.equals(networkInterface, that.networkInterface)
                && Objects.equals(ipType, that.ipType)
                && Objects.equals(heartBeatInterval, that.heartBeatInterval)
                && Objects.equals(heartBeatTimeout, that.heartBeatTimeout)
                && Objects.equals(ipDeleteTimeout, that.ipDeleteTimeout)
                && Objects.equals(logName, that.logName)
                && Objects.equals(multiple, that.multiple);
    }

    @Override
    public int hashCode() {
        return Objects.hash(service, weight, clusterName, group, namingLoadCacheAtStart, metadata, registerEnabled, ip,
                networkInterface, ipType, port, secure, heartBeatInterval, heartBeatTimeout, ipDeleteTimeout, watchDelay,
                logName, instanceEnabled, ephemeral, failureToleranceEnabled, failFast, multiple);
    }

    /**
     * 从环境变量中覆盖属性
     *
     * @param env 环境变量
     */
    private void overrideFromEnv(Environment env) {
        if (StringUtils.isEmpty(this.getLogName())) {
            this.setLogName(env.resolvePlaceholders("${spring.cloud.nacos.multi-discovery.log-name:}"));
        }
        if (StringUtils.isEmpty(this.getClusterName())) {
            this.setClusterName(env.resolvePlaceholders("${spring.cloud.nacos.multi-discovery.cluster-name:}"));
        }
        if (StringUtils.isEmpty(this.getGroup())) {
            this.setGroup(env.resolvePlaceholders("${spring.cloud.nacos.multi-discovery.group:}"));
        }
    }

    /**
     * 添加Nacos属性
     *
     * @param nacosDiscoveryProperties Nacos属性
     * @param registryName             注册中心名称
     */
    private void enrichNacosDiscoveryProperties(Properties nacosDiscoveryProperties, String registryName) {
        String prefix = PREFIX + "." + registryName;
        Map<String, Object> properties = PropertySourcesUtils.getSubProperties((ConfigurableEnvironment) environment, prefix);
        properties.forEach((k, v) -> nacosDiscoveryProperties.putIfAbsent(resolveKey(k), String.valueOf(v)));
    }

    /**
     * 解析key
     *
     * @param key key
     * @return 解析后的key
     */
    private String resolveKey(String key) {
        Matcher matcher = PATTERN.matcher(key);
        StringBuilder sb = new StringBuilder();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

}
